//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================
#include "ddk.h"
#include <stdio.h>
#include <time.h>
#include <ctype.h>
#include <malloc.h>
#include <misc.h>
#include <hash.h>
#include <llist.h>
#include <fat.h>
#include <abc.h>
#include <ChangeNotifier.h>

/*
 * ref_node()
 *    Add a reference to a node
 */
void FatDir::
ref_node(struct node *n)
{
    if (n == p_Sharedata->procroot) {
        n->n_refs += 1;
        ASSERT_DEBUG(n->n_refs != 0, "ref_node: overflow");
        return;
    }

    struct node *parentNode = n->n_dir;
    assert(parentNode != NULL);
    Ktfs_Lock(&parentNode->n_lock);
    n->n_refs += 1;
    ASSERT_DEBUG(n->n_refs != 0, "ref_node: overflow");

    if (n->n_refs == 1) {
        Ktfs_Unlock(&parentNode->n_lock);
        ref_node(parentNode);
    }
    else {
        Ktfs_Unlock(&parentNode->n_lock);
    }
}

/*
 * deref_node()
 *    Remove a reference from a node
 * Return 0 on success, error code on failed.
 *
 * Flush on last close of a dirty file
 */
int FatDir::
deref_node(struct node *n)
{
    struct clust *c;
    struct node *parentNode = NULL;
    int err = 0;

    parentNode = n->n_dir;
    /*
     *if n is root then parentNode is null.
     */
    if (parentNode) {
        Ktfs_Lock(&parentNode->n_lock);
    }

    ASSERT_DEBUG(n->n_refs > 0, "deref_node: no refs");

    /*
     * Remove ref
     */
    if ((n->n_refs -= 1) > 0) {
        /*
         * If still open, checkpoint file state into
         * directory entry if any changes have been made.
         */
        if (n->n_flags & N_DIRTY) {
            if (n->n_opmode & ACC_WRITE)
                dir_timestamp(n, 0);

            err = dir_setlen(n);
            sync();
            n->n_flags &= ~N_DIRTY;
        }
        if (parentNode) {
            Ktfs_Unlock(&parentNode->n_lock);
        }
        return err;
    }

    /*
     * Never touch root
     */
    if (n == p_Sharedata->procroot) {
        return 0;
    }
    /*
     * If he's already deleted, much of this should be skipped
     */
    c = n->n_clust;
    if (!(n->n_flags & N_DEL)) {
        /*
         * If file, remove ref from dir we're within
         */
        if ((n->n_type == T_FILE) || (n->n_type == T_SYM)) {
            hash_delete(n->n_dir->n_files, n->n_slot);
        }
        else {
            ASSERT_DEBUG(n->n_type == T_DIR,
                "deref_node: bad type");
            ASSERT_DEBUG(c->c_nclust > 0, "deref_node: short dir");
            if (c->c_nclust > 0)
                hash_delete(p_Sharedata->dirhash, c->c_clust[0]);
        }

        /*
         * Flush out dirty blocks on each last close
         */
        if (n->n_flags & N_DIRTY) {
            err = dir_setlen(n);
            sync();
        }
    }
    else {
        ASSERT_DEBUG(c->c_nclust == 0, "node_deref: del w. clusters");
        /*
         * At least the containing directory will be dirty
         */
        sync();
    }

    /*
     * Free FAT cache
     */
    pFat->free_clust(c);

    /*
     * Free file hash if a dir
     */
    if (n->n_type == T_DIR) {
        ASSERT_DEBUG(hash_size(n->n_files) == 0,
            "deref_node: dir && !empty");
        hash_dealloc(n->n_files);
    }

    /*
     * Release reference to containing node
     */
    Ktfs_Term_Lock(&n->n_lock);

    /*
     * Free our memory
     */
    free(n);
    Ktfs_Unlock(&parentNode->n_lock);
    if (err != 0)
        deref_node(parentNode);
    else
        err = deref_node(parentNode);
    return err;
}

/*
 * sync()
 *    Flush out all the junk which can be pending
 */
void FatDir::
sync(void)
{
    pFat->fat_sync();
    pDiskAbc->sync_bufs(0);
    root_sync();
    /* Notify monitor if the node is dirty who had been supervised */
    if (p_Sharedata->pNotifier != NULL)
        p_Sharedata->pNotifier->NotifyAllDirty();
}

void FatDir::Syncn(struct node *pNode)
{
    if (pNode->n_flags & N_DIRTY) {
        if (pNode->n_opmode & ACC_WRITE)
            dir_timestamp(pNode, 0);

        dir_setlen(pNode);
    }
    sync();
    pNode->n_flags &= ~N_DIRTY;
}
